#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
日志中间件

这个模块包含请求日志记录中间件：
1. 记录所有HTTP请求和响应
2. 记录请求处理时间
3. 记录请求来源和用户信息
4. 记录错误和异常
5. 支持结构化日志

提供详细的请求追踪和调试信息
支持不同日志级别和格式

作者: FastAPI 学习项目
创建时间: 2024
"""

import time
import uuid
from typing import Callable
from fastapi import Request, Response
from fastapi.responses import StreamingResponse
from starlette.middleware.base import BaseHTTPMiddleware
from loguru import logger
import json


class LoggingMiddleware(BaseHTTPMiddleware):
    """
    日志记录中间件
    
    记录所有HTTP请求和响应的详细信息，包括：
    - 请求方法、路径、查询参数
    - 请求头信息
    - 响应状态码和处理时间
    - 用户信息（如果已认证）
    - 错误信息（如果发生错误）
    """
    
    def __init__(
        self,
        app,
        *,
        log_request_body: bool = False,
        log_response_body: bool = False,
        max_body_size: int = 1024,
        exclude_paths: list = None
    ):
        """
        初始化日志中间件
        
        Args:
            app: FastAPI 应用实例
            log_request_body: 是否记录请求体
            log_response_body: 是否记录响应体
            max_body_size: 最大记录的请求/响应体大小
            exclude_paths: 排除的路径列表
        """
        super().__init__(app)
        self.log_request_body = log_request_body
        self.log_response_body = log_response_body
        self.max_body_size = max_body_size
        self.exclude_paths = exclude_paths or [
            "/health",
            "/metrics",
            "/favicon.ico",
            "/docs",
            "/redoc",
            "/openapi.json"
        ]
    
    async def dispatch(self, request: Request, call_next: Callable) -> Response:
        """
        处理请求和响应的日志记录
        
        Args:
            request: HTTP请求对象
            call_next: 下一个中间件或路由处理器
            
        Returns:
            Response: HTTP响应对象
        """
        # 生成请求ID
        request_id = str(uuid.uuid4())
        
        # 检查是否需要排除此路径
        if request.url.path in self.exclude_paths:
            return await call_next(request)
        
        # 记录请求开始时间
        start_time = time.time()
        
        # 提取请求信息
        request_info = await self._extract_request_info(request, request_id)
        
        # 记录请求开始
        logger.info(
            "请求开始",
            extra={
                "request_id": request_id,
                "event_type": "request_start",
                **request_info
            }
        )
        
        # 处理请求
        try:
            response = await call_next(request)
            
            # 计算处理时间
            process_time = time.time() - start_time
            
            # 提取响应信息
            response_info = await self._extract_response_info(response, process_time)
            
            # 记录请求完成
            log_level = "error" if response.status_code >= 400 else "info"
            getattr(logger, log_level)(
                "请求完成",
                extra={
                    "request_id": request_id,
                    "event_type": "request_complete",
                    **request_info,
                    **response_info
                }
            )
            
            # 添加请求ID到响应头
            response.headers["X-Request-ID"] = request_id
            
            return response
            
        except Exception as e:
            # 计算处理时间
            process_time = time.time() - start_time
            
            # 记录请求错误
            logger.error(
                "请求异常",
                extra={
                    "request_id": request_id,
                    "event_type": "request_error",
                    "error_type": type(e).__name__,
                    "error_message": str(e),
                    "process_time": process_time,
                    **request_info
                }
            )
            
            # 重新抛出异常
            raise
    
    async def _extract_request_info(self, request: Request, request_id: str) -> dict:
        """
        提取请求信息
        
        Args:
            request: HTTP请求对象
            request_id: 请求ID
            
        Returns:
            dict: 请求信息字典
        """
        # 基本请求信息
        info = {
            "method": request.method,
            "url": str(request.url),
            "path": request.url.path,
            "query_params": dict(request.query_params),
            "client_ip": self._get_client_ip(request),
            "user_agent": request.headers.get("user-agent", ""),
            "content_type": request.headers.get("content-type", ""),
            "content_length": request.headers.get("content-length", 0)
        }
        
        # 添加认证信息（如果存在）
        if hasattr(request.state, "user"):
            info["user_id"] = getattr(request.state.user, "id", None)
            info["username"] = getattr(request.state.user, "username", None)
        
        # 记录请求体（如果启用）
        if self.log_request_body and request.method in ["POST", "PUT", "PATCH"]:
            try:
                body = await request.body()
                if len(body) <= self.max_body_size:
                    # 尝试解析JSON
                    try:
                        info["request_body"] = json.loads(body.decode())
                    except (json.JSONDecodeError, UnicodeDecodeError):
                        info["request_body"] = body.decode("utf-8", errors="ignore")[:self.max_body_size]
                else:
                    info["request_body"] = f"<body too large: {len(body)} bytes>"
            except Exception as e:
                info["request_body_error"] = str(e)
        
        return info
    
    async def _extract_response_info(self, response: Response, process_time: float) -> dict:
        """
        提取响应信息
        
        Args:
            response: HTTP响应对象
            process_time: 处理时间（秒）
            
        Returns:
            dict: 响应信息字典
        """
        info = {
            "status_code": response.status_code,
            "process_time": round(process_time, 4),
            "response_size": len(getattr(response, "body", b"")),
            "content_type": response.headers.get("content-type", "")
        }
        
        # 记录响应体（如果启用且不是流式响应）
        if (self.log_response_body and 
            not isinstance(response, StreamingResponse) and 
            hasattr(response, "body")):
            
            try:
                body = response.body
                if len(body) <= self.max_body_size:
                    # 尝试解析JSON
                    try:
                        info["response_body"] = json.loads(body.decode())
                    except (json.JSONDecodeError, UnicodeDecodeError):
                        info["response_body"] = body.decode("utf-8", errors="ignore")[:self.max_body_size]
                else:
                    info["response_body"] = f"<body too large: {len(body)} bytes>"
            except Exception as e:
                info["response_body_error"] = str(e)
        
        return info
    
    def _get_client_ip(self, request: Request) -> str:
        """
        获取客户端IP地址
        
        Args:
            request: HTTP请求对象
            
        Returns:
            str: 客户端IP地址
        """
        # 检查代理头
        forwarded_for = request.headers.get("x-forwarded-for")
        if forwarded_for:
            return forwarded_for.split(",")[0].strip()
        
        real_ip = request.headers.get("x-real-ip")
        if real_ip:
            return real_ip
        
        # 返回直接连接的IP
        if request.client:
            return request.client.host
        
        return "unknown"


class StructuredLogger:
    """
    结构化日志记录器
    
    提供结构化的日志记录功能，便于日志分析和监控。
    """
    
    @staticmethod
    def log_api_call(
        request_id: str,
        method: str,
        path: str,
        status_code: int,
        process_time: float,
        user_id: str = None,
        error: str = None
    ):
        """
        记录API调用日志
        
        Args:
            request_id: 请求ID
            method: HTTP方法
            path: 请求路径
            status_code: 响应状态码
            process_time: 处理时间
            user_id: 用户ID
            error: 错误信息
        """
        log_data = {
            "event_type": "api_call",
            "request_id": request_id,
            "method": method,
            "path": path,
            "status_code": status_code,
            "process_time": process_time,
            "timestamp": time.time()
        }
        
        if user_id:
            log_data["user_id"] = user_id
        
        if error:
            log_data["error"] = error
            logger.error("API调用失败", extra=log_data)
        else:
            logger.info("API调用成功", extra=log_data)
    
    @staticmethod
    def log_database_operation(
        operation: str,
        table: str,
        duration: float,
        success: bool = True,
        error: str = None,
        affected_rows: int = None
    ):
        """
        记录数据库操作日志
        
        Args:
            operation: 操作类型（SELECT, INSERT, UPDATE, DELETE）
            table: 表名
            duration: 执行时间
            success: 是否成功
            error: 错误信息
            affected_rows: 影响的行数
        """
        log_data = {
            "event_type": "database_operation",
            "operation": operation,
            "table": table,
            "duration": duration,
            "success": success,
            "timestamp": time.time()
        }
        
        if affected_rows is not None:
            log_data["affected_rows"] = affected_rows
        
        if error:
            log_data["error"] = error
            logger.error("数据库操作失败", extra=log_data)
        else:
            logger.info("数据库操作成功", extra=log_data)
    
    @staticmethod
    def log_authentication(
        user_id: str,
        username: str,
        action: str,
        success: bool,
        ip_address: str,
        user_agent: str = None,
        error: str = None
    ):
        """
        记录认证日志
        
        Args:
            user_id: 用户ID
            username: 用户名
            action: 认证动作（login, logout, register, etc.）
            success: 是否成功
            ip_address: IP地址
            user_agent: 用户代理
            error: 错误信息
        """
        log_data = {
            "event_type": "authentication",
            "user_id": user_id,
            "username": username,
            "action": action,
            "success": success,
            "ip_address": ip_address,
            "timestamp": time.time()
        }
        
        if user_agent:
            log_data["user_agent"] = user_agent
        
        if error:
            log_data["error"] = error
            logger.warning("认证失败", extra=log_data)
        else:
            logger.info("认证成功", extra=log_data)
    
    @staticmethod
    def log_security_event(
        event_type: str,
        severity: str,
        description: str,
        ip_address: str = None,
        user_id: str = None,
        additional_data: dict = None
    ):
        """
        记录安全事件日志
        
        Args:
            event_type: 事件类型
            severity: 严重程度（low, medium, high, critical）
            description: 事件描述
            ip_address: IP地址
            user_id: 用户ID
            additional_data: 额外数据
        """
        log_data = {
            "event_type": "security_event",
            "security_event_type": event_type,
            "severity": severity,
            "description": description,
            "timestamp": time.time()
        }
        
        if ip_address:
            log_data["ip_address"] = ip_address
        
        if user_id:
            log_data["user_id"] = user_id
        
        if additional_data:
            log_data.update(additional_data)
        
        # 根据严重程度选择日志级别
        if severity in ["high", "critical"]:
            logger.error("安全事件", extra=log_data)
        elif severity == "medium":
            logger.warning("安全事件", extra=log_data)
        else:
            logger.info("安全事件", extra=log_data)